/**
 * Copyright (C) 2008+ Spark Zheng
 *
 * @file	include/str.h
 * @brief
 *      A str module need to work as String class in CPP.
 *      a clone from glib may be is enough.
 *
 * @see		dlib
 * @module      dlib
 *
 * @history
 *      version 0.1.0	Spark init str module, add str_trim/pack/toupper
 *      version 0.1.2	Spark add endian handle of pack & unpack
 *      version 0.1.3	Spark add ip_to_str/str_to_ip
 */

#ifndef _DLIB_STR_H
#define _DLIB_STR_H

#ifdef __cplusplus
extern "C" {
#endif

#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <netinet/in.h>

#include "global.h"

typedef struct str_token	str_token_t;
struct str_token {
	char	*value;		/** value with \0 */
	size_t	length;		/** length of value */
};

/**
 * str_tokenize
 * @brief tokenize a changable-string into tokens
 *	  sample: domain:ip:port => return 4, 
 *		  tokens[0].value is "domain", length is 6,
 *		  tokens[1].value is "ip", length is 2,
 *		  tokens[2].value is "port", length is 4,
 *		  tokens[3].value is NULL, length is 0,
 * @return the number of tokens, must <= max_tokens
 * @param str: string, Caution: must be changable
 * @param delim: token delimiter
 * @param tokens: the token array
 * @param max_tokens: the token array length
 */
static inline size_t str_tokenize(char *str, char delim, str_token_t *tokens,
				  size_t max_tokens)
{
	char *s, *e;
	size_t ntokens = 0;

	for (s = e = str; ntokens < max_tokens - 1; ++e) {
		if (*e == delim) {
			if (s != e) {
				tokens[ntokens].value = s;
				tokens[ntokens].length = e - s;
				ntokens++;
				*e = '\0';
			}
			s = e + 1;
		} else if (*e == '\0') {
			if (s != e) {
				tokens[ntokens].value = s;
				tokens[ntokens].length = e - s;
				ntokens++;
			}
			break;
		}
	}
	tokens[ntokens].value =  *e == '\0' ? NULL : e;
	tokens[ntokens].length = 0;
	ntokens++;

	return ntokens;
}

/**
 * str_index
 * @brief find the first index of a char in a string
 * @return -1: not found, >0: the first index
 * @param str: source string
 * @param len: source string length
 * @param ch: distination charactor
 */
static inline int str_index(const char *str, int len, char ch)
{
	int i = 0;
	for (; (i < len) && (str[i] != ch); i++);
	return (i >= len) ? -1 : i; 
}

/**
 * str_compare
 * @brief compare two strings
 * @return 0(if one == two) / -1 (if one < two)/ 1(if one > two)
 * @param s_one: string one
 * @param s_two: string two
 */
static inline int str_compare(const char *s_one, const char *s_two)
{
	if (s_one == NULL && s_two == NULL)
		return 0;
	else if (s_one == NULL)
		return -1;
	else if (s_two == NULL)
		return 1;
	
	while ((*s_one | *s_two) != 0) {
		if (*s_one < *s_two)
			return -1;
		else if (*s_one > *s_two)
			return 1;
		else {
			s_one++;
			s_two++;
		}
	}
	return 0;
}

/**
 * str_upper
 * @brief transform lower letter to lower letter, return changed string
 * @param str: string buffer
 */
static inline char *str_upper(char *str)
{
	if (str == NULL)
		return NULL;
	char *c;

	for (c = str; *c; c++) {
		if (*c >= 'a' && *c <= 'z')
			*c &= ~32;
	}

	return str;
}

/**
 * str_lower
 * @brief transform upper letter to lower letter, return changed string
 * @param str: string buffer
 */
static inline char *str_lower(char *str)
{
	if (str == NULL)
		return NULL;
	char *c;

	for (c = str; *c; c++) {
		if (*c >= 'A' && *c <= 'Z')
			*c |= 32;
	}

	return str;
}

/**
 * dlib_snprintf
 * @brief an implement of snprintf, act different from snprint when size <= actual size:
 * @return if size > actual size, 
 *	return value will be actual size, and the buf[act_size] will be '\0'
 * @return if size <= actual size,
 *	return value will be size -1 and buf[size -1] will be '\0'
 * @param buf: buf not NULL
 * @param size: size of buf
 * @param format: string format
 */
static inline int dlib_snprintf(char *buf, size_t size, const char *format, ...)
{
	int ret;
	va_list ap;

	va_start(ap, format);
	ret = vsnprintf(buf, size, format, ap);

	va_end(ap);
	if (ret < (int)size) {
		return ret;
	}
	return size - 1;
}

/**
 * str_trim
 * @brief Like php str_trim and perl chomp function, return original string
 * @param str: str which need to be trimmed
 */
extern char *str_trim(char *str);

/**
 * str_ltrim
 * @brief Like php str_trim and perl chomp function, return original string
 * @param str: str which need to be trimmed
 */
extern char *str_ltrim(char *str);

/**
 * str_rtrim
 * @brief Like php str_trim and perl chomp function, return original string
 * @param str: str which need to be trimmed
 */
extern char *str_rtrim(char *str);

/**
 * str_pack
 * @brief Like php and perl pack function
 * @param buf: string buffer
 * @param fmt: arguments need to be packed
 */
extern int str_pack(char *buf, char *fmt, ...);

/**
 * str_unpack
 * @brief Like php and perl unpack function
 * @param buf: string buffer
 * @param fmt: arguments need to be unpacked
 */
extern int str_unpack(char *buf, char *fmt, ...);

/**
 * str_replace
 * @brief replace a substring in string, return the number of replacement
 * @param line: string buffer
 * @param buff_size: string buffer size
 * @param orig: the original substring
 * @param replace: the replacement substring
 */
extern int str_replace(char *line, size_t buff_size,
		       const char *orig, const char *replace);

/**
 * str_to_ipv4
 * @convert string (192.168.0.1) to ip(32bit, ipv4)
 * @return ip
 * @param ip_str: string
 */
extern in_addr_t str_to_ipv4(const char *ip_str);

/**
 * ipv4_to_str
 * @convert ip(32bit, ipv4) to string (192.168.0.1)
 * @return string or NULL
 * @param ip: the ipv4 number
 * @param ip_str[out]: out string, caution: must be >= INET_ADDRSTRLEN
 */
extern char *ipv4_to_str(in_addr_t ip, char *ip_str);

/**
 * str_to_ulong
 * @brief convert string to unsigned long
 * @return <=0 err, 1 success
 * @param str: string
 * @param out[out]: out unsigned long interger
 */
extern int str_to_ulong(const char *str, unsigned long *out);

/**
 * str_to_long
 * @brief convert str to long
 * @return <=0 error, 1 success
 * @param str: string
 * @param out[out]: out long interger
 */
extern int str_to_long(const char *str, long *out);

#ifdef __cplusplus
}
#endif
#endif
